package org.apache.lucene.util;
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Simple lockless and memory barrier free String intern cache that is guaranteed
 * to return the same String instance as String.intern()
 * does.
 *
 * @lucene.internal
 */
public class SimpleStringInterner extends StringInterner {

    private static class Entry {
        final private String str;
        final private int hash;
        private Entry next;

        private Entry(String str, int hash, Entry next) {
            this.str = str;
            this.hash = hash;
            this.next = next;
        }
    }

    private final Entry[] cache;
    private final int maxChainLength;

    /**
     * @param tableSize  Size of the hash table, should be a power of two.
     * @param maxChainLength  Maximum length of each bucket, after which the oldest item inserted is dropped.
     */
    public SimpleStringInterner(int tableSize, int maxChainLength) {
        cache = new Entry[Math.max(1, BitUtil.nextHighestPowerOfTwo(tableSize))];
        this.maxChainLength = Math.max(2, maxChainLength);
    }

    @Override
    public String intern(String s) {
        int h = s.hashCode();
        // In the future, it may be worth augmenting the string hash
        // if the lower bits need better distribution.
        int slot = h & (cache.length - 1);

        Entry first = this.cache[slot];
        Entry nextToLast = null;

        int chainLength = 0;

        for (Entry e = first; e != null; e = e.next) {
            if (e.hash == h && (e.str == s || e.str.compareTo(s) == 0)) {
                // if (e.str == s || (e.hash == h && e.str.compareTo(s)==0)) {
                return e.str;
            }

            chainLength++;
            if (e.next != null) {
                nextToLast = e;
            }
        }

        // insertion-order cache: add new entry at head
        s = s.intern();
        this.cache[slot] = new Entry(s, h, first);
        if (chainLength >= maxChainLength) {
            // prune last entry
            nextToLast.next = null;
        }
        return s;
    }
}